﻿using System;
using System.Globalization;

namespace Fwql.Net.Extensions
{
    public static class DateTimeExtension
    {
        ///<summary>
        ///	取得系统UTC Offset
        ///</summary>
        public static double UtcOffset
        {
            get { return DateTime.Now.Subtract(DateTime.UtcNow).TotalHours; }
        }

        /// <summary>
        /// 计算年龄
        /// </summary>
        /// <param name = "dateOfBirth">生日</param>
        /// <returns>年龄</returns>
        public static int CalculateAge(this DateTime dateOfBirth)
        {
            return CalculateAge(dateOfBirth, DateTime.Today);
        }

        /// <summary>
        /// 计算年龄
        /// </summary>
        /// <param name = "dateOfBirth">生日</param>
        /// <param name = "referenceDate">参考日期</param>
        /// <returns>年龄</returns>
        public static int CalculateAge(this DateTime dateOfBirth, DateTime referenceDate)
        {
            var years = referenceDate.Year - dateOfBirth.Year;
            if (referenceDate.Month < dateOfBirth.Month || (referenceDate.Month == dateOfBirth.Month && referenceDate.Day < dateOfBirth.Day))
                --years;
            return years;
        }

        /// <summary>
        /// 判断是否是闰年
        /// </summary>
        /// <param name = "date">日期</param>
        /// <returns></returns>
        public static bool IsLeapYear(this DateTime date)
        {
            return date.Year%4 == 0 && (date.Year%100 != 0 || date.Year%400 == 0);
        }

        /// <summary>
        /// 取得当月天数
        /// </summary>
        /// <param name = "date"></param>
        /// <returns></returns>
        public static int DaysCountOfMonth(this DateTime date)
        {
            if (IsLeapYear(date) && date.Month == 2)
                return 29;
            if (date.Month == 2)
                return 28;
            if (date.Month == 4 || date.Month == 6 || date.Month == 9 || date.Month == 11)
                return 30;
            return 31;
        }

        /// <summary>
        /// 取得当月的最后一天
        /// </summary>
        /// <param name = "date"></param>
        /// <returns></returns>
        public static DateTime LastDayOfMonth(this DateTime date)
        {
            int day = DaysCountOfMonth(date);
            return new DateTime(date.Year, date.Month, day);
        }

        /// <summary>
        /// 取得当月的最后一个星期几
        /// </summary>
        /// <param name="date">日期</param>
        /// <param name="dayOfWeek">星期几</param>
        /// <returns></returns>
        public static DateTime LastWeekdayOfMonth(this DateTime date, DayOfWeek dayOfWeek)
        {
            var dt = date.LastDayOfMonth();
            while (dt.DayOfWeek != dayOfWeek)
                dt = dt.AddDays(-1);
            return dt;
        }

        /// <summary>
        /// 取得当月的第一天
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime FirstDayOfMonth(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, 1);
        }

        /// <summary>
        /// 取得当月的第一个星期几
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "dayOfWeek">星期几</param>
        /// <returns>The date time</returns>
        public static DateTime FirstWeekdayOfMonth(this DateTime date, DayOfWeek dayOfWeek)
        {
            var dt = date.FirstDayOfMonth();
            while (dt.DayOfWeek != dayOfWeek)
                dt = dt.AddDays(1);
            return dt;
        }

        /// <summary>
        /// 判断是否为当月第一天
        /// </summary>
        /// <param name = "date"></param>
        /// <returns></returns>
        public static bool IsFirstDayOfMonth(this DateTime date)
        {
            DateTime dt = FirstDayOfMonth(date);
            return dt.Day == date.Day;
        }

        /// <summary>
        /// 判断是否为当月最后一天
        /// </summary>
        /// <param name = "date"></param>
        /// <returns></returns>
        public static bool IsLastDayOfMonth(this DateTime date)
        {
            int lastDayValueOfMonth = DaysCountOfMonth(date);
            return lastDayValueOfMonth == date.Day;
        }

        /// <summary>
        /// 判断是否今日
        /// </summary>
        /// <param name = "dt">日期</param>
        /// <returns> </returns>
        public static bool IsToday(this DateTime dt)
        {
            return (dt.Date == DateTime.Today);
        }

        /// <summary>
        /// 设置时间
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "hours">时</param>
        /// <param name = "minutes">分</param>
        /// <param name = "seconds">秒</param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime date, int hours, int minutes, int seconds)
        {
            return date.SetTime(new TimeSpan(hours, minutes, seconds));
        }

        /// <summary>
        /// 设置时间
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "time">时间间隔</param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime date, TimeSpan time)
        {
            return date.Date.Add(time);
        }

        /// <summary>
        /// 转DateTimeOffset
        /// </summary>
        /// <param name = "localDateTime">日期</param>
        /// <param name = "localTimeZone">时区</param>
        /// <returns></returns>
        public static DateTimeOffset ToDateTimeOffset(this DateTime localDateTime, TimeZoneInfo localTimeZone = null)
        {
            if (localDateTime.Kind != DateTimeKind.Unspecified)
                localDateTime = new DateTime(localDateTime.Ticks, DateTimeKind.Unspecified);

            return TimeZoneInfo.ConvertTimeToUtc(localDateTime, localTimeZone ?? TimeZoneInfo.Local);
        }

        /// <summary>
        /// 取得本周的第一天
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "cultureInfo">文化</param>
        /// <returns></returns>
        public static DateTime FirstDayOfWeek(this DateTime date, CultureInfo cultureInfo = null)
        {
            DayOfWeek firstWeekday = (cultureInfo ?? CultureInfo.CurrentCulture).DateTimeFormat.FirstDayOfWeek;
            while (date.DayOfWeek != firstWeekday)
                date = date.AddDays(-1);

            return date;
        }

        /// <summary>
        /// 取得本周的最后一天
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "cultureInfo">文化</param>
        /// <returns></returns>
        public static DateTime LastDayOfWeek(this DateTime date, CultureInfo cultureInfo = null)
        {
            return date.FirstDayOfWeek(cultureInfo).AddDays(6);
        }

        /// <summary>
        /// 取得本周的星期几
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "weekday">星期几</param>
        /// <returns></returns>
        public static DateTime DayOfWeek(this DateTime date, DayOfWeek weekday)
        {
            return date.DayOfWeek(weekday, null);
        }

        /// <summary>
        /// 取得本周的星期几
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "weekday">星期几</param>
        /// <param name = "cultureInfo">文化</param>
        /// <returns></returns>
        public static DateTime DayOfWeek(this DateTime date, DayOfWeek weekday, CultureInfo cultureInfo)
        {
            var firstDayOfWeek = date.FirstDayOfWeek(cultureInfo);
            while (firstDayOfWeek.DayOfWeek != weekday)
                firstDayOfWeek = firstDayOfWeek.AddDays(1);
            return firstDayOfWeek;
        }

        /// <summary>
        /// 取得下一个星期几
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "weekday">星期几</param>
        /// <returns></returns>
        public static DateTime NextDayOfWeek(this DateTime date, DayOfWeek weekday)
        {
            while (date.DayOfWeek != weekday)
                date = date.AddDays(1);
            return date;
        }

        /// <summary>
        /// 取得上一个星期几
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "weekday">星期几</param>
        /// <returns></returns>
        public static DateTime PreviousWeekday(this DateTime date, DayOfWeek weekday)
        {
            while (date.DayOfWeek != weekday)
                date = date.AddDays(-1);
            return date;
        }

        /// <summary>
        /// 判断日期是否相同，忽略时间部分
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "dateToCompare">日期</param>
        public static bool IsDateEqual(this DateTime date, DateTime dateToCompare)
        {
            return (date.Date == dateToCompare.Date);
        }

        /// <summary>
        /// 判断时间是否相同，忽略日期部分
        /// </summary>
        /// <param name = "time">时间</param>
        /// <param name = "timeToCompare">时间</param>
        /// <returns></returns>
        public static bool IsTimeEqual(this DateTime time, DateTime timeToCompare)
        {
            return (time.TimeOfDay == timeToCompare.TimeOfDay);
        }

        /// <summary>
        /// 取得与1970-01-01相差的时间间隔
        /// </summary>
        /// <param name = "datetime"></param>
        /// <returns></returns>
        public static TimeSpan TimeSpanSince1970(this DateTime datetime)
        {
            DateTime date1970 = new DateTime(1970, 1, 1);
            return datetime.Subtract(date1970);
        }

        /// <summary>
        /// 判断是否为周末(周六，周日)
        /// </summary>
        /// <param name = "date">日期</param>
        /// <returns></returns>
        public static bool IsWeekend(this DateTime date)
        {
            return date.DayOfWeek.EqualsAny(System.DayOfWeek.Saturday, System.DayOfWeek.Sunday);
        }

        /// <summary>
        /// 增加若干星期
        /// </summary>
        /// <param name = "date">日期</param>
        /// <param name = "value">增加的星期数</param>
        /// <returns></returns>
        public static DateTime AddWeeks(this DateTime date, int value)
        {
            return date.AddDays(value*7);
        }

        ///<summary>
        ///	返回当前日期所属年份的总天数
        ///</summary>
        ///<param name = "date">日期</param>
        ///<returns></returns>
        public static int DaysOfYear(this DateTime date)
        {
            var first = new DateTime(date.Year, 1, 1);
            var last = new DateTime(date.Year + 1, 1, 1);
            return DaysBetween(first, last);
        }

        ///<summary>
        ///	返回两个日期之间相差的总天数
        ///</summary>
        ///<param name = "fromDate">日期</param>
        ///<param name = "toDate">日期</param>
        ///<returns></returns>
        public static int DaysBetween(this DateTime fromDate, DateTime toDate)
        {
            return Convert.ToInt32(toDate.Subtract(fromDate).TotalDays);
        }

        /// <summary>
        /// 返回当前年份的星期总数
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static int WeeksOfYear(this DateTime dateTime)
        {
            var culture = CultureInfo.CurrentUICulture;
            var calendar = culture.Calendar;
            var dateTimeFormat = culture.DateTimeFormat;

            return calendar.GetWeekOfYear(dateTime, dateTimeFormat.CalendarWeekRule, dateTimeFormat.FirstDayOfWeek);
        }

        #region DateDiff

        /// <summary>
        /// 时间间隔比较枚举
        /// </summary>
        public enum DateInterval
        {
            /// <summary>
            /// 天
            /// </summary>
            Day,
            /// <summary>
            /// 小时
            /// </summary>
            Hour,
            /// <summary>
            /// 分钟
            /// </summary>
            Minute,
            /// <summary>
            /// 月
            /// </summary>
            Month,
            /// <summary>
            /// 季度
            /// </summary>
            Quarter,
            /// <summary>
            /// 秒
            /// </summary>
            Second,
            /// <summary>
            /// 星期(=天/7)
            /// </summary>
            Weekday,
            /// <summary>
            /// 当年的第几星期
            /// </summary>
            WeekOfYear,
            /// <summary>
            /// 年
            /// </summary>
            Year
        }

        /// <summary>
        /// 时间间隔差
        /// </summary>
        /// <param name="interval">时间间隔比较枚举</param>
        /// <param name="sourceDate"></param>
        /// <param name="compareDate"></param>
        /// <returns></returns>
        public static long DateDiff(this DateTime sourceDate,
                                    DateTime compareDate,
                                    DateInterval interval = DateInterval.Day)
        {
            if (interval == DateInterval.Year)
                return compareDate.Year - sourceDate.Year;

            if (interval == DateInterval.Month)
                return (compareDate.Month - sourceDate.Month) + (12*(compareDate.Year - sourceDate.Year));

            TimeSpan ts = compareDate - sourceDate;

            if (interval == DateInterval.Day)
                return Round(ts.TotalDays);

            if (interval == DateInterval.Hour)
                return Round(ts.TotalHours);

            if (interval == DateInterval.Minute)
                return Round(ts.TotalMinutes);

            if (interval == DateInterval.Second)
                return Round(ts.TotalSeconds);

            if (interval == DateInterval.Weekday)
            {
                return Round(ts.TotalDays/7.0);
            }

            if (interval == DateInterval.WeekOfYear)
            {
                while (compareDate.DayOfWeek != System.DayOfWeek.Monday)
                {
                    compareDate = compareDate.AddDays(-1);
                }
                while (sourceDate.DayOfWeek != System.DayOfWeek.Monday)
                {
                    sourceDate = sourceDate.AddDays(-1);
                }
                ts = compareDate - sourceDate;
                return Round(ts.TotalDays/7.0);
            }

            if (interval == DateInterval.Quarter)
            {
                double d1Quarter = GetQuarter(sourceDate);
                double d2Quarter = GetQuarter(compareDate);
                double d1 = d2Quarter - d1Quarter;
                double d2 = (4*(compareDate.Year - sourceDate.Year));
                return Round(d1 + d2);
            }

            return 0;
        }

        private static long Round(double dVal)
        {
            if (dVal >= 0)
                return (long) Math.Floor(dVal);
            return (long) Math.Ceiling(dVal);
        }

        /// <summary>
        /// 取得当前时间是在第？季度
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static int GetQuarter(this DateTime date)
        {
            return (date.Month + 2)/3;
        }

        #endregion
    }
}